
package com.neo.tiny.bpm.service.task.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.neo.tiny.admin.api.dept.DeptApi;
import com.neo.tiny.admin.api.user.AdminUserApi;
import com.neo.tiny.admin.dto.dept.SysDeptDTO;
import com.neo.tiny.admin.dto.user.UserInfoDTO;
import com.neo.tiny.bpm.dto.message.BpmMessageSendWhenProcessInstanceApproveReqDTO;
import com.neo.tiny.bpm.dto.message.BpmMessageSendWhenProcessInstanceRejectReqDTO;
import com.neo.tiny.bpm.dto.task.BpmProcessInstanceCreateReqDTO;
import com.neo.tiny.bpm.entity.BpmProcessDefinitionExtDO;
import com.neo.tiny.bpm.entity.BpmProcessInstanceExtDO;
import com.neo.tiny.bpm.enums.instance.BpmProcessInstanceDeleteReasonEnum;
import com.neo.tiny.bpm.enums.instance.BpmProcessInstanceStatusEnum;
import com.neo.tiny.bpm.enums.task.BpmProcessInstanceResultEnum;
import com.neo.tiny.bpm.flowable.event.bean.BpmProcessInstanceResultEvent;
import com.neo.tiny.bpm.flowable.event.publisher.BpmProcessInstanceResultEventPublisher;
import com.neo.tiny.bpm.mapper.BpmProcessInstanceExtMapper;
import com.neo.tiny.bpm.message.BpmMessageService;
import com.neo.tiny.bpm.service.process.BpmProcessDefinitionService;
import com.neo.tiny.bpm.service.task.BpmProcessInstanceExtService;
import com.neo.tiny.bpm.service.task.BpmTaskService;
import com.neo.tiny.bpm.vo.instance.BpmProcessInstanceCreateReqVO;
import com.neo.tiny.bpm.vo.instance.BpmProcessInstancePageItemRespVO;
import com.neo.tiny.bpm.vo.instance.BpmProcessInstancePageReqVO;
import com.neo.tiny.bpm.vo.instance.BpmProcessInstanceRespVO;
import com.neo.tiny.common.constant.ErrorCodeConstants;
import com.neo.tiny.common.exception.WebApiException;
import com.neo.tiny.common.util.NumberUtils;
import com.neo.tiny.query.LambdaQueryWrapperBase;
import com.neo.tiny.resolver.CommonPage;
import com.neo.tiny.util.MyBatisUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.delegate.event.FlowableCancelledEvent;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.flowable.variable.api.history.HistoricVariableInstance;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.validation.Valid;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author yqz
 * @Description: 流程实例
 * @date 2022-10-07 21:41:44
 */
@Slf4j
@Service
@AllArgsConstructor
public class BpmProcessInstanceExtServiceImpl extends ServiceImpl<BpmProcessInstanceExtMapper, BpmProcessInstanceExtDO>
        implements BpmProcessInstanceExtService {

    private final BpmProcessInstanceExtMapper instanceExtMapper;
    private final BpmTaskService taskService;
    private final BpmProcessDefinitionService definitionService;

    private final RuntimeService runtimeService;

    private final HistoryService historyService;

    private final BpmProcessInstanceResultEventPublisher instanceResultEventPublisher;

    private final BpmMessageService messageService;

    private final BpmProcessInstanceResultEventPublisher processInstanceResultEventPublisher;

    private final AdminUserApi adminUserApi;

    private final DeptApi deptApi;

    @Override
    public Page<BpmProcessInstancePageItemRespVO> getMyProcessInstancePage(Long userId, BpmProcessInstancePageReqVO vo) {
        // 通过 BpmProcessInstanceExtDO 表，先查询到对应的分页
        Page<BpmProcessInstanceExtDO> page = instanceExtMapper.selectPage(MyBatisUtils.buildPage(vo), new LambdaQueryWrapperBase<BpmProcessInstanceExtDO>()
                .eqIfPresent(BpmProcessInstanceExtDO::getStartUserId, userId)
                .likeIfPresent(BpmProcessInstanceExtDO::getName, vo.getName())
                .eqIfPresent(BpmProcessInstanceExtDO::getProcessDefinitionId, vo.getProcessDefinitionId())
                .eqIfPresent(BpmProcessInstanceExtDO::getCategory, vo.getCategory())
                .eqIfPresent(BpmProcessInstanceExtDO::getStatus, vo.getStatus())
                .eqIfPresent(BpmProcessInstanceExtDO::getResult, vo.getResult())
                .betweenIfPresent(BpmProcessInstanceExtDO::getCreateTime, vo.getBeginCreateTime(), vo.getEndCreateTime())
                .orderByDesc(BpmProcessInstanceExtDO::getId));
        if (page.getTotal() == 0) {
            return new Page<>();
        }

        // 获得流程 Task Map
        Set<String> processInstanceIds = page.getRecords()
                .stream()
                .map(BpmProcessInstanceExtDO::getProcessInstanceId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        Map<String, List<Task>> taskMap = taskService.getTaskMapByProcessInstanceIds(processInstanceIds);
        List<BpmProcessInstancePageItemRespVO> voList = convertTask(page.getRecords(), taskMap);

        Page<BpmProcessInstancePageItemRespVO> newPage = CommonPage.restPage(page, BpmProcessInstancePageItemRespVO.class);
        newPage.setRecords(voList);
        return newPage;
    }

    private List<BpmProcessInstancePageItemRespVO> convertTask(List<BpmProcessInstanceExtDO> list, Map<String, List<Task>> taskMap) {

        if (Objects.isNull(list)) {
            return null;
        }
        List<BpmProcessInstancePageItemRespVO> voList = list.stream().map(extDO -> {
            BpmProcessInstancePageItemRespVO vo = new BpmProcessInstancePageItemRespVO();
            vo.setId(extDO.getProcessInstanceId());
            vo.setName(extDO.getName());
            vo.setProcessDefinitionId(extDO.getProcessDefinitionId());
            vo.setCategory(extDO.getCategory());
            vo.setStatus(extDO.getStatus());
            vo.setResult(extDO.getResult());
            Optional.ofNullable(extDO.getCreateTime()).ifPresent(createTime -> {
                vo.setCreateTime(Date.from(createTime.atZone(ZoneId.systemDefault()).toInstant()));
            });
            Optional.ofNullable(extDO.getEndTime()).ifPresent(endTime -> {
                vo.setEndTime(Date.from(endTime.atZone(ZoneId.systemDefault()).toInstant()));
            });
            return vo;
        }).collect(Collectors.toList());

        voList.forEach(vo -> {
            List<Task> tasks = taskMap.get(vo.getId());
            if (CollUtil.isNotEmpty(tasks)) {
                List<BpmProcessInstancePageItemRespVO.Task> voTaskList = tasks.stream().map(task -> {
                    BpmProcessInstancePageItemRespVO.Task voTask = new BpmProcessInstancePageItemRespVO.Task();
                    voTask.setId(task.getId());
                    voTask.setName(task.getName());
                    return voTask;
                }).collect(Collectors.toList());
                vo.setTasks(voTaskList);
            }
        });

        return voList;
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) {
        // 获得流程定义
        ProcessDefinition definition = definitionService.getProcessDefinition(createReqVO.getProcessDefinitionId());
        // 发起流程
        return createProcessInstance(userId, definition, createReqVO.getVariables(), null);
    }


    private String createProcessInstance(Long userId, ProcessDefinition definition,
                                         Map<String, Object> variables, String businessKey) {
        // 校验流程定义
        if (definition == null) {
            throw new WebApiException(ErrorCodeConstants.PROCESS_DEFINITION_NOT_EXISTS);
        }
        if (definition.isSuspended()) {
            throw new WebApiException(ErrorCodeConstants.PROCESS_DEFINITION_IS_SUSPENDED);
        }

        // 创建流程实例
        /**
         * {@link com.neo.tiny.bpm.flowable.listener.BpmProcessInstanceEventListener}
         */
        ProcessInstance instance = runtimeService.startProcessInstanceById(definition.getId(), businessKey, variables);
        Map<String, Object> processVariables = instance.getProcessVariables();
        // 设置流程名字
        runtimeService.setProcessInstanceName(instance.getId(), definition.getName());

        // 补全流程实例的拓展表
        BpmProcessInstanceExtDO extDO = new BpmProcessInstanceExtDO();
        extDO.setProcessInstanceId(instance.getId());
        extDO.setFormVariables(variables);
        instanceExtMapper.update(extDO, new LambdaQueryWrapperBase<BpmProcessInstanceExtDO>()
                .eq(BpmProcessInstanceExtDO::getProcessInstanceId, extDO.getProcessInstanceId()));
        return instance.getId();
    }

    @Override
    public String createProcessInstance(Long userId, BpmProcessInstanceCreateReqDTO createReqDTO) {
        // 获得流程定义
        ProcessDefinition definition = definitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey());
        // 发起流程

        return createProcessInstance(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey());
    }

    /**
     * 获得历史的流程实例
     *
     * @param instanceId 流程实例的编号
     * @return 历史的流程实例
     */
    @Override
    public HistoricProcessInstance getHistoricProcessInstance(String instanceId) {
        return historyService.createHistoricProcessInstanceQuery().processInstanceId(instanceId).singleResult();
    }

    @Override
    public void createProcessInstanceExt(ProcessInstance instance) {
        // 获得流程定义
        ProcessDefinition definition = definitionService.getProcessDefinition2(instance.getProcessDefinitionId());
        // 插入 BpmProcessInstanceExtDO 对象
        BpmProcessInstanceExtDO instanceExtDO = BpmProcessInstanceExtDO.builder()
                .processInstanceId(instance.getId())
                .processDefinitionId(definition.getId())
                .name(instance.getProcessDefinitionName())
                .startUserId(Long.valueOf(instance.getStartUserId()))
                .category(definition.getCategory())
                .status(BpmProcessInstanceStatusEnum.RUNNING.getStatus())
                .result(BpmProcessInstanceResultEnum.PROCESS.getResult()).build();

        instanceExtMapper.insert(instanceExtDO);
    }


    @Override
    public void updateProcessInstanceExtCancel(FlowableCancelledEvent event) {
        // 判断是否为 Reject 不通过。如果是，则不进行更新.
        // 因为，updateProcessInstanceExtReject 方法，已经进行更新了
        if (BpmProcessInstanceDeleteReasonEnum.isRejectReason((String) event.getCause())) {
            return;
        }

        // 需要主动查询，因为 instance 只有 id 属性
        // 另外，此时如果去查询 ProcessInstance 的话，字段是不全的，所以去查询了 HistoricProcessInstance
        HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId());
        // 更新拓展表
        BpmProcessInstanceExtDO instanceExtDO = BpmProcessInstanceExtDO.builder()
                .processInstanceId(event.getProcessInstanceId())
                // 由于 ProcessInstance 里没有办法拿到 endTime，所以这里设置
                .endTime(LocalDateTime.now())
                .status(BpmProcessInstanceStatusEnum.FINISH.getStatus())
                .result(BpmProcessInstanceResultEnum.CANCEL.getResult()).build();

        instanceExtMapper.update(instanceExtDO, new LambdaQueryWrapperBase<BpmProcessInstanceExtDO>()
                .eq(BpmProcessInstanceExtDO::getProcessInstanceId, instanceExtDO.getProcessInstanceId()));

        // 发送流程实例的状态事件
        BpmProcessInstanceResultEvent resultEvent = new BpmProcessInstanceResultEvent(this);
        resultEvent.setId(processInstance.getId());
        resultEvent.setProcessDefinitionKey(processInstance.getProcessDefinitionKey());
        resultEvent.setBusinessKey(processInstance.getBusinessKey());
        resultEvent.setResult(instanceExtDO.getResult());
        instanceResultEventPublisher.sendProcessInstanceResultEvent(resultEvent);
    }

    @Override
    public void updateProcessInstanceExtComplete(ProcessInstance instance) {
        // 需要主动查询，因为 instance 只有 id 属性
        // 另外，此时如果去查询 ProcessInstance 的话，字段是不全的，所以去查询了 HistoricProcessInstance
        HistoricProcessInstance historicProcessInstance = getHistoricProcessInstance(instance.getId());
        // 更新拓展表
        BpmProcessInstanceExtDO ext = new BpmProcessInstanceExtDO();
        ext.setProcessInstanceId(instance.getProcessInstanceId());
        // 由于 ProcessInstance 里没有办法拿到 endTime，所以这里设置
        ext.setEndTime(LocalDateTime.now());
        ext.setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus());
        // 如果正常完全，说明审批通过
        ext.setResult(BpmProcessInstanceResultEnum.APPROVE.getResult());
        instanceExtMapper.update(ext, new LambdaQueryWrapperBase<BpmProcessInstanceExtDO>()
                .eq(BpmProcessInstanceExtDO::getProcessInstanceId, instance.getProcessInstanceId()));
        // 发送流程被不通过的消息 TODO 是否可以利用反射，配置接入方class
        BpmMessageSendWhenProcessInstanceApproveReqDTO dto = new BpmMessageSendWhenProcessInstanceApproveReqDTO();
        dto.setProcessInstanceId(instance.getId());
        dto.setProcessInstanceName(instance.getName());
        dto.setStartUserId(NumberUtils.parseUpperCaseLong(instance.getStartUserId()));
        messageService.sendMessageWhenProcessInstanceApprove(dto);
        // 发送流程实例的状态事件
        BpmProcessInstanceResultEvent event = new BpmProcessInstanceResultEvent(this);
        event.setId(historicProcessInstance.getId());
        event.setProcessDefinitionKey(historicProcessInstance.getProcessDefinitionKey());
        event.setBusinessKey(historicProcessInstance.getBusinessKey());
        event.setResult(ext.getResult());
        processInstanceResultEventPublisher.sendProcessInstanceResultEvent(event);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateProcessInstanceExtReject(String processInstanceId, String reason) {
        // 需要主动查询，因为 instance 只有 id 属性
        ProcessInstance instance = getProcessInstance(processInstanceId);
        // 删除流程实例，以实现驳回任务时，取消整个审批流程
        deleteProcessInstance(processInstanceId, reason);
        // 更新 status + result
        // 注意，不能和上面的逻辑更换位置。因为 deleteProcessInstance 会触发流程的取消，进而调用 updateProcessInstanceExtCancel 方法，
        // 设置 result 为 BpmProcessInstanceStatusEnum.CANCEL，显然和 result 不一定是一致的
        BpmProcessInstanceExtDO ext = new BpmProcessInstanceExtDO();
        ext.setProcessInstanceId(processInstanceId);
        ext.setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus());
        ext.setResult(BpmProcessInstanceResultEnum.REJECT.getResult());
        instanceExtMapper.update(ext, new LambdaQueryWrapperBase<BpmProcessInstanceExtDO>()
                .eq(BpmProcessInstanceExtDO::getProcessInstanceId, processInstanceId));
        // 发送流程被不通过的消息 TODO 是否可以利用反射，配置接入方class
        BpmMessageSendWhenProcessInstanceRejectReqDTO rejectReqDTO = new BpmMessageSendWhenProcessInstanceRejectReqDTO();
        rejectReqDTO.setProcessInstanceId(instance.getId());
        rejectReqDTO.setProcessInstanceName(instance.getName());
        rejectReqDTO.setReason(reason);
        rejectReqDTO.setStartUserId(NumberUtils.parseUpperCaseLong(instance.getStartUserId()));
        messageService.sendMessageWhenProcessInstanceReject(rejectReqDTO);
        // 发送流程实例的状态事件
        BpmProcessInstanceResultEvent event = new BpmProcessInstanceResultEvent(this);
        event.setId(instance.getId());
        event.setProcessDefinitionKey(instance.getProcessDefinitionKey());
        event.setBusinessKey(instance.getBusinessKey());
        event.setResult(ext.getResult());
        processInstanceResultEventPublisher.sendProcessInstanceResultEvent(event);
    }

    /**
     * 删除流程实例
     *
     * @param processInstanceId 流程实例id
     * @param reason            原因
     */
    private void deleteProcessInstance(String processInstanceId, String reason) {
        runtimeService.deleteProcessInstance(processInstanceId, reason);
    }


    @Override
    public BpmProcessInstanceRespVO getProcessInstanceVO(String instanceId) {
        // 获得流程实例
        HistoricProcessInstance historicProcessInstance = getHistoricProcessInstance(instanceId);
        if (historicProcessInstance == null) {
            return null;
        }
        // 获取流程流程实例绑定的表单变量
        List<HistoricVariableInstance> list = historyService.createHistoricVariableInstanceQuery()
                .processInstanceId(instanceId)
                .orderByVariableName().desc()
                .list();
        BpmProcessInstanceExtDO instanceExt = instanceExtMapper.selectOne(new LambdaQueryWrapperBase<BpmProcessInstanceExtDO>()
                .eq(BpmProcessInstanceExtDO::getProcessInstanceId, instanceId));
        Assert.notNull(instanceExt, "流程实例拓展({}) 不存在", instanceId);
        // 获得流程定义
        ProcessDefinition processDefinition = definitionService.getProcessDefinition(historicProcessInstance.getProcessDefinitionId());
        Assert.notNull(processDefinition, "流程定义({}) 不存在", historicProcessInstance.getProcessDefinitionId());

        BpmProcessDefinitionExtDO processDefinitionExt = definitionService.getProcessDefinitionExt(historicProcessInstance.getProcessDefinitionId());

        String bpmnXml = definitionService.getProcessDefinitionBpmnXml(historicProcessInstance.getProcessDefinitionId());
        UserInfoDTO userInfoDTO = Optional.ofNullable(historicProcessInstance.getStartUserId()).map(startUseId ->
                adminUserApi.getUser(Long.valueOf(startUseId))).orElse(null);

        SysDeptDTO deptDTO = null;
        if (!Objects.isNull(userInfoDTO)) {
            deptDTO = deptApi.getDept(userInfoDTO.getDeptId());
        }

        return convertBpmProcessInstanceResp(historicProcessInstance, instanceExt,
                processDefinition, processDefinitionExt,
                bpmnXml, userInfoDTO, deptDTO);
    }

    @Override
    public ProcessInstance getProcessInstance(String id) {
        return runtimeService.createProcessInstanceQuery().processInstanceId(id).singleResult();
    }

    @Override
    public List<ProcessInstance> getProcessInstances(Set<String> ids) {
        return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list();
    }

    @Override
    public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) {
        return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list();
    }

    private BpmProcessInstanceRespVO convertBpmProcessInstanceResp(HistoricProcessInstance processInstance, BpmProcessInstanceExtDO processInstanceExt,
                                                                   ProcessDefinition processDefinition, BpmProcessDefinitionExtDO processDefinitionExt,
                                                                   String bpmnXml, UserInfoDTO startUser, SysDeptDTO dept) {

        BpmProcessInstanceRespVO vo = new BpmProcessInstanceRespVO();
        BeanUtil.copyProperties(processInstance, vo);
        // 获取表单数据
        BeanUtil.copyProperties(processInstanceExt, vo, "id");

        BpmProcessInstanceRespVO.ProcessDefinition definition = new BpmProcessInstanceRespVO.ProcessDefinition();
        definition.setId(processDefinition.getId());

        BeanUtil.copyProperties(processDefinitionExt, definition, "id");
        definition.setBpmnXml(bpmnXml);
        vo.setProcessDefinition(definition);


        if (!Objects.isNull(startUser)) {
            BpmProcessInstanceRespVO.User user = new BpmProcessInstanceRespVO.User();
            user.setId(startUser.getId());
            user.setNickname(startUser.getNickName());
            user.setDeptId(startUser.getDeptId());
            if (!Objects.isNull(dept)) {
                user.setDeptName(dept.getDeptName());
            }
            vo.setStartUser(user);
        }
        return vo;
    }
}
